home *** CD-ROM | disk | FTP | other *** search
- ### -*- python -*-
- ### Code to generate "Reverse Wrappers", i.e. C->Python wrappers
- ### (C) 2004 Gustavo Carneiro <gjc@gnome.org>
- import argtypes
-
- def join_ctype_name(ctype, name):
- '''Joins a C type and a variable name into a single string'''
- if ctype[-1] != '*':
- return " ".join((ctype, name))
- else:
- return "".join((ctype, name))
-
-
- class CodeSink(object):
- def __init__(self):
- self.indent_level = 0 # current indent level
- self.indent_stack = [] # previous indent levels
-
- def _format_code(self, code):
- assert isinstance(code, str)
- l = []
- for line in code.split('\n'):
- l.append(' '*self.indent_level + line)
- if l[-1]:
- l.append('')
- return '\n'.join(l)
-
- def writeln(self, line=''):
- raise NotImplementedError
-
- def indent(self, level=4):
- '''Add a certain ammount of indentation to all lines written
- from now on and until unindent() is called'''
- self.indent_stack.append(self.indent_level)
- self.indent_level += level
-
- def unindent(self):
- '''Revert indentation level to the value before last indent() call'''
- self.indent_level = self.indent_stack.pop()
-
-
- class FileCodeSink(CodeSink):
- def __init__(self, fp):
- CodeSink.__init__(self)
- assert isinstance(fp, file)
- self.fp = fp
-
- def writeln(self, line=''):
- self.fp.write(self._format_code(line))
-
- class MemoryCodeSink(CodeSink):
- def __init__(self):
- CodeSink.__init__(self)
- self.lines = []
-
- def writeln(self, line=''):
- self.lines.append(self._format_code(line))
-
- def flush_to(self, sink):
- assert isinstance(sink, CodeSink)
- for line in self.lines:
- sink.writeln(line.rstrip())
- self.lines = []
-
- def flush(self):
- l = []
- for line in self.lines:
- l.append(self._format_code(line))
- self.lines = []
- return "".join(l)
-
- class ReverseWrapper(object):
- '''Object that generates a C->Python wrapper'''
- def __init__(self, cname, is_static=True):
- assert isinstance(cname, str)
-
- self.cname = cname
- ## function object we will call, or object whose method we will call
- self.called_pyobj = None
- ## name of method of self.called_pyobj we will call
- self.method_name = None
- self.is_static = is_static
-
- self.parameters = []
- self.declarations = MemoryCodeSink()
- self.body = MemoryCodeSink()
- self.cleanup_actions = []
- self.pyargv_items = []
- self.pyargv_optional_items = []
-
- def set_call_target(self, called_pyobj, method_name=None):
- assert called_pyobj is not None
- assert self.called_pyobj is None
- self.called_pyobj = called_pyobj
- self.method_name = method_name
-
- def set_return_type(self, return_type):
- assert isinstance(return_type, ReturnType)
- self.return_type = return_type
-
- def add_parameter(self, param):
- assert isinstance(param, Parameter)
- self.parameters.append(param)
-
- def add_declaration(self, decl_code):
- self.declarations.writeln(decl_code)
-
- def add_pyargv_item(self, variable, optional=False):
- if optional:
- self.pyargv_optional_items.append(variable)
- else:
- self.pyargv_items.append(variable)
-
- def write_code(self, code,
- cleanup=None,
- failure_expression=None,
- failure_cleanup=None):
- '''Add a chunk of code with cleanup and error handling
-
- This method is to be used by TypeHandlers when generating code
-
- Keywork arguments:
- code -- code to add
- cleanup -- code to cleanup any dynamic resources created by @code
- (except in case of failure) (default None)
- failure_expression -- C boolean expression to indicate
- if anything failed (default None)
- failure_cleanup -- code to cleanup any dynamic resources
- created by @code in case of failure (default None)
- '''
- if code is not None:
- self.body.writeln(code)
- if failure_expression is not None:
- self.body.writeln("if (%s) {" % failure_expression)
- self.body.indent()
- self.body.writeln("if (PyErr_Occurred())")
- self.body.indent()
- self.body.writeln("PyErr_Print();")
- self.body.unindent()
- if failure_cleanup is not None:
- self.body.writeln(failure_cleanup)
- for cleanup_action in self.cleanup_actions:
- self.body.writeln(cleanup_action)
- self.return_type.write_error_return()
- self.body.unindent()
- self.body.writeln("}")
- if cleanup is not None:
- self.cleanup_actions.insert(0, cleanup)
-
- def generate(self, sink):
- '''Generate the code into a CodeSink object'''
- assert isinstance(sink, CodeSink)
-
- self.add_declaration("PyGILState_STATE __py_state;")
- self.write_code(code="__py_state = pyg_gil_state_ensure();",
- cleanup="pyg_gil_state_release(__py_state);")
-
- for param in self.parameters:
- param.convert_c2py()
-
- assert self.called_pyobj is not None,\
- "Parameters failed to provide a target function or method."
-
- if self.is_static:
- sink.writeln('static %s' % self.return_type.get_c_type())
- else:
- sink.writeln(self.return_type.get_c_type())
- c_proto_params = map(Parameter.format_for_c_proto, self.parameters)
- sink.writeln("%s(%s)\n{" % (self.cname, ", ".join(c_proto_params)))
-
- self.return_type.write_decl()
- self.add_declaration("PyObject *py_retval;")
-
- ## Handle number of arguments
- if self.pyargv_items:
- self.add_declaration("PyObject *py_args;")
- py_args = "py_args"
- if self.pyargv_optional_items:
- self.add_declaration("int argc = %i;" % len(self.pyargv_items))
- argc = "argc"
- for arg in self.pyargv_optional_items:
- self.body.writeln("if (%s)" % arg)
- self.body.indent()
- self.body.writeln("++argc;")
- self.body.unindent()
- else:
- argc = str(len(self.pyargv_items))
- else:
- if self.pyargv_optional_items:
- self.add_declaration("PyObject *py_args;")
- py_args = "py_args"
- self.add_declaration("int argc = 0;")
- argc = "argc"
- for arg in self.pyargv_optional_items:
- self.body.writeln("if (%s)" % arg)
- self.body.indent()
- self.body.writeln("++argc;")
- self.body.unindent()
- else:
- py_args = "NULL"
- argc = None
-
- self.body.writeln()
-
- if py_args != "NULL":
- self.write_code("py_args = PyTuple_New(%s);" % argc,
- cleanup="Py_DECREF(py_args);")
- pos = 0
- for arg in self.pyargv_items:
- try: # try to remove the Py_DECREF cleanup action, if we can
- self.cleanup_actions.remove("Py_DECREF(%s);" % arg)
- except ValueError: # otherwise we have to Py_INCREF..
- self.body.writeln("Py_INCREF(%s);" % arg)
- self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg))
- pos += 1
- for arg in self.pyargv_optional_items:
- self.body.writeln("if (%s) {" % arg)
- self.body.indent()
- try: # try to remove the Py_DECREF cleanup action, if we can
- self.cleanup_actions.remove("Py_XDECREF(%s);" % arg)
- except ValueError: # otherwise we have to Py_INCREF..
- self.body.writeln("Py_INCREF(%s);" % arg)
- self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg))
- self.body.unindent()
- self.body.writeln("}")
- pos += 1
-
- self.body.writeln()
-
- # call it
- if self.method_name is None:
- self.write_code("py_retval = PyObject_Call(%s, %s);"
- % (self.called_pyobj, py_args),
- cleanup="Py_DECREF(py_retval);",
- failure_expression="!py_retval")
- else:
- self.add_declaration("PyObject *py_method;")
- self.write_code("py_method = PyObject_GetAttrString(%s, \"%s\");"
- % (self.called_pyobj, self.method_name),
- cleanup="Py_DECREF(py_method);",
- failure_expression="!py_method")
- self.write_code("py_retval = PyObject_CallObject(py_method, %s);"
- % (py_args,),
- cleanup="Py_DECREF(py_retval);",
- failure_expression="!py_retval")
-
- self.return_type.write_conversion()
-
- sink.indent()
- self.declarations.flush_to(sink)
- sink.writeln()
- self.body.flush_to(sink)
- sink.writeln()
- for cleanup_action in self.cleanup_actions:
- sink.writeln(cleanup_action)
- if self.return_type.get_c_type() != 'void':
- sink.writeln()
- sink.writeln("return retval;")
- sink.unindent()
- sink.writeln("}")
-
- class TypeHandler(object):
- def __init__(self, wrapper, **props):
- assert isinstance(wrapper, ReverseWrapper)
- self.wrapper = wrapper
- self.props = props
-
- class ReturnType(TypeHandler):
-
- def get_c_type(self):
- raise NotImplementedError
-
- def write_decl(self):
- raise NotImplementedError
-
- def write_error_return(self):
- '''Write "return <value>" code in case of error'''
- raise NotImplementedError
-
- def write_conversion(self):
- '''Writes code to convert Python return value in 'py_retval'
- into C 'retval'. Returns a string with C boolean expression
- that determines if anything went wrong. '''
- raise NotImplementedError
-
- class Parameter(TypeHandler):
-
- def __init__(self, wrapper, name, **props):
- TypeHandler.__init__(self, wrapper, **props)
- self.name = name
-
- def get_c_type(self):
- raise NotImplementedError
-
- def convert_c2py(self):
- '''Write some code before calling the Python method.'''
- pass
-
- def format_for_c_proto(self):
- return join_ctype_name(self.get_c_type(), self.name)
-
-
- ###---
- class StringParam(Parameter):
-
- def get_c_type(self):
- return self.props.get('c_type', 'char *').replace('const-', 'const ')
-
- def convert_c2py(self):
- if self.props.get('optional', False):
- self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name)
- self.wrapper.write_code(code=("if (%s)\n"
- " py_%s = PyString_FromString(%s);\n"
- % (self.name, self.name, self.name)),
- cleanup=("Py_XDECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name, optional=True)
- else:
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = PyString_FromString(%s);" %
- (self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name),
- failure_expression=("!py_%s" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- for ctype in ('char*', 'gchar*', 'const-char*', 'char-const*', 'const-gchar*',
- 'gchar-const*', 'string', 'static_string'):
- argtypes.matcher.register_reverse(ctype, StringParam)
-
-
- class StringReturn(ReturnType):
-
- def get_c_type(self):
- return "char *"
-
- def write_decl(self):
- self.wrapper.add_declaration("char *retval;")
-
- def write_error_return(self):
- self.wrapper.write_code("return NULL;")
-
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression="!PyString_Check(py_retval)",
- failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be a string");')
- self.wrapper.write_code("retval = g_strdup(PyString_AsString(py_retval));")
-
- for ctype in ('char*', 'gchar*'):
- argtypes.matcher.register_reverse(ctype, StringReturn)
-
-
-
- class VoidReturn(ReturnType):
-
- def get_c_type(self):
- return "void"
-
- def write_decl(self):
- pass
-
- def write_error_return(self):
- self.wrapper.write_code("return;")
-
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression="py_retval != Py_None",
- failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be None");')
-
- argtypes.matcher.register_reverse_ret('void', VoidReturn)
- argtypes.matcher.register_reverse_ret('none', VoidReturn)
-
- class GObjectParam(Parameter):
-
- def get_c_type(self):
- return self.props.get('c_type', 'GObject *')
-
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name)
- self.wrapper.write_code(code=("if (%s)\n"
- " py_%s = pygobject_new((GObject *) %s);\n"
- "else {\n"
- " Py_INCREF(Py_None);\n"
- " py_%s = Py_None;\n"
- "}"
- % (self.name, self.name, self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse('GObject*', GObjectParam)
-
- class GObjectReturn(ReturnType):
-
- def get_c_type(self):
- return self.props.get('c_type', 'GObject *')
-
- def write_decl(self):
- self.wrapper.add_declaration("%s retval;" % self.get_c_type())
-
- def write_error_return(self):
- self.wrapper.write_code("return NULL;")
-
- def write_conversion(self):
- self.wrapper.write_code("retval = (%s) pygobject_get(py_retval);"
- % self.get_c_type())
- self.wrapper.write_code("g_object_ref((GObject *) retval);")
-
- argtypes.matcher.register_reverse_ret('GObject*', GObjectReturn)
-
-
-
- class IntParam(Parameter):
-
- def get_c_type(self):
- return self.props.get('c_type', 'int')
-
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = PyInt_FromLong(%s);" %
- (self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- class IntReturn(ReturnType):
- def get_c_type(self):
- return self.props.get('c_type', 'int')
- def write_decl(self):
- self.wrapper.add_declaration("%s retval;" % self.get_c_type())
- def write_error_return(self):
- self.wrapper.write_code("return -G_MAXINT;")
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression="!PyInt_Check(py_retval)",
- failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be an int");')
- self.wrapper.write_code("retval = PyInt_AsLong(py_retval);")
-
- for argtype in ('int', 'gint', 'guint', 'short', 'gshort', 'gushort', 'long',
- 'glong', 'gsize', 'gssize', 'guint8', 'gint8', 'guint16',
- 'gint16', 'gint32', 'GTime'):
- argtypes.matcher.register_reverse(argtype, IntParam)
- argtypes.matcher.register_reverse_ret(argtype, IntReturn)
-
-
- class GEnumReturn(IntReturn):
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression=("pyg_enum_get_value(%s, py_retval, (gint *)&retval)" %
- self.props['typecode']))
-
- argtypes.matcher.register_reverse_ret("GEnum", GEnumReturn)
-
- class GEnumParam(IntParam):
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = pyg_enum_from_gtype(%s, %s);" %
- (self.name, self.props['typecode'], self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name),
- failure_expression=("!py_%s" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("GEnum", GEnumParam)
-
- class GFlagsReturn(IntReturn):
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression=("pyg_flags_get_value(%s, py_retval, (gint *)&retval)" %
- self.props['typecode']))
-
- argtypes.matcher.register_reverse_ret("GFlags", GFlagsReturn)
-
- class GFlagsParam(IntParam):
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = pyg_flags_from_gtype(%s, %s);" %
- (self.name, self.props['typecode'], self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name),
- failure_expression=("!py_%s" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("GFlags", GFlagsParam)
-
-
- class GtkTreePathParam(IntParam):
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = pygtk_tree_path_to_pyobject(%s);" %
- (self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name),
- failure_expression=("!py_%s" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("GtkTreePath*", GtkTreePathParam)
-
-
- class BooleanReturn(ReturnType):
- def get_c_type(self):
- return "gboolean"
- def write_decl(self):
- self.wrapper.add_declaration("gboolean retval;")
- def write_error_return(self):
- self.wrapper.write_code("return FALSE;")
- def write_conversion(self):
- self.wrapper.write_code("retval = PyObject_IsTrue(py_retval)? TRUE : FALSE;")
- argtypes.matcher.register_reverse_ret("gboolean", BooleanReturn)
-
- class BooleanParam(Parameter):
- def get_c_type(self):
- return "gboolean"
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code("py_%s = %s? Py_True : Py_False;"
- % (self.name, self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("gboolean", BooleanParam)
-
-
- class DoubleParam(Parameter):
- def get_c_type(self):
- return self.props.get('c_type', 'gdouble')
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = PyFloat_FromDouble(%s);" %
- (self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- class DoubleReturn(ReturnType):
- def get_c_type(self):
- return self.props.get('c_type', 'gdouble')
- def write_decl(self):
- self.wrapper.add_declaration("%s retval;" % self.get_c_type())
- def write_error_return(self):
- self.wrapper.write_code("return -G_MAXFLOAT;")
- def write_conversion(self):
- self.wrapper.write_code(
- code=None,
- failure_expression="!PyFloat_AsDouble(py_retval)",
- failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be a float");')
- self.wrapper.write_code("retval = PyFloat_AsDouble(py_retval);")
-
- for argtype in ('float', 'double', 'gfloat', 'gdouble'):
- argtypes.matcher.register_reverse(argtype, DoubleParam)
- argtypes.matcher.register_reverse_ret(argtype, DoubleReturn)
-
-
- class GBoxedParam(Parameter):
- def get_c_type(self):
- return self.props.get('c_type').replace('const-', 'const ')
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- ctype = self.get_c_type()
- if ctype.startswith('const '):
- ctype_no_const = ctype[len('const '):]
- self.wrapper.write_code(
- code=('py_%s = pyg_boxed_new(%s, (%s) %s, TRUE, TRUE);' %
- (self.name, self.props['typecode'],
- ctype_no_const, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- else:
- self.wrapper.write_code(
- code=('py_%s = pyg_boxed_new(%s, %s, FALSE, FALSE);' %
- (self.name, self.props['typecode'], self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("GBoxed", GBoxedParam)
-
- class GBoxedReturn(ReturnType):
- def get_c_type(self):
- return self.props.get('c_type')
- def write_decl(self):
- self.wrapper.add_declaration("%s retval;" % self.get_c_type())
- def write_error_return(self):
- self.wrapper.write_code("return retval;")
- def write_conversion(self):
- self.wrapper.write_code(
- failure_expression=("!pyg_boxed_check(py_retval, %s)" %
- (self.props['typecode'],)),
- failure_cleanup=('PyErr_SetString(PyExc_TypeError, "retval should be a %s");'
- % (self.props['typename'],)))
- self.wrapper.write_code('retval = pyg_boxed_get(py_retval, %s);' %
- self.props['typename'])
-
- argtypes.matcher.register_reverse_ret("GBoxed", GBoxedReturn)
-
-
- class GdkRectanglePtrParam(Parameter):
- def get_c_type(self):
- return self.props.get('c_type').replace('const-', 'const ')
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(
- code=('py_%(name)s = Py_BuildValue("(ffff)", %(name)s->x, %(name)s->y,\n'
- ' %(name)s->width, %(name)s->height);'
- % dict(name=self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name))
- self.wrapper.add_pyargv_item("py_%s" % self.name)
-
- argtypes.matcher.register_reverse("GdkRectangle*", GdkRectanglePtrParam)
-
-
- class PyGObjectMethodParam(Parameter):
- def __init__(self, wrapper, name, method_name, **props):
- Parameter.__init__(self, wrapper, name, **props)
- self.method_name = method_name
-
- def get_c_type(self):
- return self.props.get('c_type', 'GObject *')
-
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
- self.wrapper.write_code(code=("py_%s = pygobject_new((GObject *) %s);" %
- (self.name, self.name)),
- cleanup=("Py_DECREF(py_%s);" % self.name),
- failure_expression=("!py_%s" % self.name))
- self.wrapper.set_call_target("py_%s" % self.name, self.method_name)
-
- class CallbackInUserDataParam(Parameter):
- def __init__(self, wrapper, name, free_it, **props):
- Parameter.__init__(self, wrapper, name, **props)
- self.free_it = free_it
-
- def get_c_type(self):
- return "gpointer"
-
- def convert_c2py(self):
- self.wrapper.add_declaration("PyObject **_user_data;")
- cleanup = self.free_it and ("g_free(%s);" % self.name) or None
- self.wrapper.write_code(code=("_real_user_data = (PyObject **) %s;"
- % self.name),
- cleanup=cleanup)
-
- self.wrapper.add_declaration("PyObject *py_func;")
- cleanup = self.free_it and "Py_DECREF(py_func);" or None
- self.wrapper.write_code(code="py_func = _user_data[0];",
- cleanup=cleanup)
- self.wrapper.set_call_target("py_func")
-
- self.wrapper.add_declaration("PyObject *py_user_data;")
- cleanup = self.free_it and "Py_XDECREF(py_user_data);" or None
- self.wrapper.write_code(code="py_user_data = _user_data[1];",
- cleanup=cleanup)
- self.wrapper.add_pyargv_item("py_user_data", optional=True)
-
- def _test():
- import sys
-
- wrapper = ReverseWrapper("this_is_the_c_function_name", is_static=True)
- wrapper.set_return_type(StringReturn(wrapper))
- wrapper.add_parameter(PyGObjectMethodParam(wrapper, "self", method_name="do_xxx"))
- wrapper.add_parameter(StringParam(wrapper, "param2", optional=True))
- wrapper.add_parameter(GObjectParam(wrapper, "param3"))
- wrapper.generate(FileCodeSink(sys.stderr))
-
- wrapper = ReverseWrapper("this_a_callback_wrapper")
- wrapper.set_return_type(VoidReturn(wrapper))
- wrapper.add_parameter(StringParam(wrapper, "param1", optional=False))
- wrapper.add_parameter(GObjectParam(wrapper, "param2"))
- wrapper.add_parameter(CallbackInUserDataParam(wrapper, "data", free_it=True))
- wrapper.generate(FileCodeSink(sys.stderr))
-
- if __name__ == '__main__':
- _test()
-